package com.bangcommunity.bbframe.common.utils;

import com.bangcommunity.bbframe.common.utils.lang.StringUtils;
import com.bangcommunity.bbframe.common.utils.reflect.ReflectionWrap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 打印对象方法
 *
 * @author tanghc
 * @since 2015/11/6.
 */
public class ToStringUtil {
    static Logger logger = LoggerFactory.getLogger(ToStringUtil.class);
    static ReflectionWrap reflectionWrap = new ReflectionWrap();

    /**
     * 连接多个字符串
     */
    public static String join(Object... objs) {
        return joinWith("", objs);
    }

    /**
     * 连接多个字符串
     */
    public static String joinWith(String split, Object... objs) {
        StringBuffer sbuf = new StringBuffer();
        if (null != objs) {
            int i = 0;
            for (Object o : objs) {
                if (i++ != 0) {
                    sbuf.append(split);
                }
                sbuf.append(tripToEmptyString(o));
            }
        }
        return sbuf.toString();
    }

    /**
     * 生成tostring字符串。格式为
     * Bean[field1=value1,field2=value2,filed3=Field3Bean[field3BeanFeild1=value1]]
     * 属性值如果是jdk中的类型，直接调用tostring方法，如果是自定义的类型，递归调用该方法。
     *
     * @param bean
     * @return
     */
    public static String reflectionToString(Object bean) {
        return reflectionToString(bean, 100, 1024);
    }

    public static String reflectionToString(Object bean, Map<Object, Object> hasReflect) {
        return reflectionToString(bean, 100, 1024, hasReflect);
    }

    public static String reflectionToString(Object bean, int argLength, int totalMaxLength) {
        Map<Object, Object> hasReflect = new HashMap<>();
        return reflectionToString(bean, 100, 1024, hasReflect);
    }

    public static String reflectionToString(Object bean, int argLength, int totalMaxLength,
            Map<Object, Object> hasReflect) {
        if (null == bean) {
            return null;
        }
        if (hasReflect.get(bean) != null) {
            return null;
        }
        hasReflect.put(bean, bean);
        StringBuffer sbuf = new StringBuffer();
        try {
            Class<?> beanClass = bean.getClass();
            if (hasOverrideToString(beanClass)) {
                return bean.toString();
            }
            sbuf.append(beanClass.getSimpleName());
            sbuf.append("{");
            if (Collection.class.isAssignableFrom(beanClass)) {
                sbuf.append(join((Collection) bean, ",", hasReflect));
            } else if (Map.class.isAssignableFrom(beanClass)) {
                sbuf.append(joinMap((Map) bean, ",", hasReflect));
            } else if (beanClass.isArray()) {
                sbuf.append(joinArray(bean, ",", hasReflect));
            } else if (beanClass.isEnum() || isJavaClass(beanClass)) {
                return bean.toString();
            } else {
                Map<String, Field> fields = reflectionWrap.getFields(beanClass);
                if (null != fields) {
                    int i = 0;
                    for (Map.Entry<String, Field> entry : fields.entrySet()) {
                        String key = entry.getKey();
                        Field field = entry.getValue();
                        if (null != field) {
                            ToStringIgnor tostringIgnor = field.getAnnotation(ToStringIgnor.class);
                            if (null != tostringIgnor) {
                                continue;
                            }
                            field.setAccessible(true);
                            Object fieldValue = reflectionWrap.invokeField(field, bean);
                            if (i++ != 0) {
                                sbuf.append(" ,");
                            }
                            sbuf.append(key);
                            sbuf.append("=");
                            String s = reflectionToString(fieldValue, argLength, totalMaxLength, hasReflect);
                            if (StringUtils.isNotBlank(s) && s.length() > argLength) {
                                s = s.substring(0, argLength) + "...";
                            }
                            sbuf.append(s);
                        }
                    }
                }
            }
            sbuf.append("}");
        } catch (Exception e) {
            logger.error("buildToString error: bean={}", new Object[] { bean, e });
        }
        String s = sbuf.toString();
        if (StringUtils.isNotBlank(s) && s.length() > totalMaxLength) {
            s = s.substring(0, totalMaxLength) + "...";
        }
        return s;
    }

    private static boolean isJavaClass(Class clz) {
        return clz != null && clz.getClassLoader() == null;
    }

    public static String join(Collection collection, String separator) {
        return join(collection, separator, new HashMap<Object, Object>());
    }

    public static String join(Collection collection, String separator, Map<Object, Object> hasReflect) {
        if (null == collection) {
            return null;
        }
        StringBuffer sbuf = new StringBuffer();
        if (null != collection) {
            try {
                int i = 0;
                Iterator iterator = collection.iterator();
                while (iterator.hasNext()) {
                    if (i++ != 0) {
                        sbuf.append(separator);
                    }
                    Object next = iterator.next();
                    sbuf.append(reflectionToString(next, hasReflect));
                }
            } catch (Exception e) {
                sbuf.append(collection);
            }
        }
        return sbuf.toString();
    }

    public static String joinMap(Map<Object, Object> map, String separator) {
        return joinMap(map, separator, new HashMap<Object, Object>());
    }

    public static String joinMap(Map<Object, Object> map, String separator, Map<Object, Object> hasReflect) {
        if (null == map) {
            return null;
        }
        StringBuffer sbuf = new StringBuffer();
        if (null != map) {
            try {
                int i = 0;
                for (Map.Entry<Object, Object> entry : map.entrySet()) {
                    if (i++ != 0) {
                        sbuf.append(separator);
                    }
                    sbuf.append(reflectionToString(entry.getKey(), hasReflect));
                    sbuf.append("=");
                    sbuf.append(reflectionToString(entry.getValue(), hasReflect));
                }
            } catch (Exception e) {
                sbuf.append(map);
            }
        }
        return sbuf.toString();
    }

    public static String joinArray(Object array, String separator) {
        return joinArray(array, separator, new HashMap<Object, Object>());
    }

    public static String joinArray(Object array, String separator, Map<Object, Object> hasReflect) {
        if (null == array) {
            return null;
        }
        if (!array.getClass().isArray()) {
            return array.toString();
        }
        StringBuffer sbuf = new StringBuffer();
        try {
            int length = Array.getLength(array);
            for (int i = 0; i < length; i++) {
                if (i != 0) {
                    sbuf.append(separator);
                }
                sbuf.append(reflectionToString(Array.get(array, i), hasReflect));
            }
        } catch (Exception e) {
            sbuf.append(array);
        }
        return sbuf.toString();
    }

    public static void main(String[] args) {
        Map<String, Object> map = new HashMap<>();
        map.put("aaa", new Object());
        map.put("bbb", "3333");
        Object[] obs = new Object[] { new Object(), "aaaa", map };
        System.out.println(reflectionToString(obs));

        System.out.println(getChatCount("中文abc123！@#"));
        System.out.println(getChatCount2("中文abc123"));
    }

    public static boolean hasOverrideMethod(String method, Class clazz, Class<?>... parameterTypes) {
        if (null == clazz || null == method) {
            return false;
        }
        try {
            Method declaredMethod = clazz.getDeclaredMethod(method, parameterTypes);
            if (null != declaredMethod) {
                return true;
            }
        } catch (NoSuchMethodException e) {
            return false;
        }
        return false;
    }

    public static boolean hasOverrideToString(Class clazz) {
        return hasOverrideMethod("toString", clazz);
    }

    public static Object tripToEmpty(Object o) {
        if (null == o) {
            return "";
        }
        return o;
    }

    public static String tripToEmptyString(Object o) {
        if (null == o) {
            return "";
        }
        return o.toString();
    }

    /**
     * 计算字符长度，中文算两个字符
     * 
     * @param s
     * @return
     */
    public static int getChatCount(String s) {
        if (null == s) {
            return 0;
        }
        s = s.replaceAll("[^\\x00-\\xff]", "**");
        int length = s.length();
        return length;
    }

    public static int getChatCount2(String s) {
        if (null == s) {
            return 0;
        }
        int length = 0;
        for (int i = 0; i < s.length(); i++) {
            int ascii = Character.codePointAt(s, i);
            if (ascii >= 0 && ascii <= 255)
                length++;
            else
                length += 2;
        }
        return length;
    }

    public static String filterEmoji(String source, String replaceChar) {
        if (source != null) {
            Pattern emoji = Pattern.compile("[ud83cudc00-ud83cudfff]|[ud83dudc00-ud83dudfff]|[u2600-u27ff]",
                    Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE);
            Matcher emojiMatcher = emoji.matcher(source);
            if (emojiMatcher.find()) {
                source = emojiMatcher.replaceAll(replaceChar);
                return source;
            }
            return source;
        }
        return source;
    }
}
